home *** CD-ROM | disk | FTP | other *** search
/ Libris Britannia 4 / science library(b).zip / science library(b) / INFO / INTSHARE.ZIP / INTSHARE.DOC
Text File  |  1991-08-06  |  21KB  |  496 lines

  1.                    IBM'S INTERRUPT-SHARING PROTOCOL
  2.                             by Chris Dunford
  3.                                  8/6/91
  4.  
  5. In the PS/2 BIOS Interface Technical Reference, IBM has suggested a
  6. protocol for the sharing of system interrupts.  Although the protocol
  7. was intended to allow sharing of hardware interrupts, it is equally
  8. usable for software interrupts.
  9.  
  10. One of the features of the interrupt sharing protocol is that it
  11. permits a resident program to "unhook" itself from an interrupt even if
  12. it is not the first interrupt handler in the chain of handlers.  The
  13. benefit of this should be immediately apparent to developers of TSRs.
  14. It is a commonplace in TSR manuals to see verbiage along these lines:
  15.  
  16.     Program X can be unloaded from memory by typing ... at the DOS
  17.     prompt.  For this to work, however, X must be the last TSR loaded.
  18.     If other resident software is loaded after X, you cannot unload X.
  19.  
  20. The interrupt sharing protocol eliminates this restriction.
  21.  
  22. However, for the protocol to work, it must be followed by the majority
  23. of TSR writers.  To date, this has not occurred.  Because the protocol
  24. is easy to implement and inexpensive in terms of memory, I feel that at
  25. least part of the reason for this must be that the protocol has not
  26. been widely publicized; most DOS programmers are simply unaware of it.
  27. I am offering this document as a modest attempt to let DOS programmers
  28. know that a solution exists for a longstanding problem.
  29.  
  30. Let me add as a caveat that I do not have and have not examined the
  31. primary source for this information--the PS/2 BIOS reference.  Most of
  32. the information in this paper was gleaned from other sources, augmented
  33. by my own experiences in writing TSRs.  The most recent writeup as of
  34. this date (August 6, 1991) was in the 7/91 issue of the Microsoft
  35. Systems Journal.
  36.  
  37. This document is not copyrighted.  Its distribution in any form is
  38. encouraged (please try to avoid making a profit on it).  If you make
  39. changes, please make sure they are clearly marked so that I get blame
  40. only for my own errors and credit only where it is due.  Please try to
  41. get any changes, amplifications, corrections, etc., back to me so that
  42. a clean copy can be redistributed if necessary.
  43.  
  44. The perpetrator of this document is:
  45.  
  46.     Chris Dunford
  47.     The Cove Software Group
  48.     PO Box 1072
  49.     Columbia, MD 21044
  50.     301/992-9371
  51.  
  52.     CompuServe: 76703,2002
  53.     Internet: 76703.2002@compuserve.com
  54.  
  55. THE PROBLEM
  56.  
  57. The vast majority of terminate-and-stay-resident (TSR) programs need to
  58. intercept one or more hardware or software interrupts.  Shown below is
  59. typical code for accomplishing this, assuming that the interrupt to be
  60. "hooked" is the DOS service interrupt (INT 21h):
  61.  
  62.      (data)
  63.         OldInt21    dd ?
  64.  
  65.      (installation code)
  66.         ; Save current INT 21h vector
  67.         mov ax,3521h
  68.         int 21h
  69.         mov word ptr OldInt21,bx
  70.         mov word ptr OldInt21+2,es
  71.  
  72.         ; Set new INT 21h vector
  73.         mov dx,offset NewInt21
  74.         mov ax,2521h
  75.         int 21h
  76.         ...
  77.  
  78.     (interrupt handler)
  79.     NewInt21:
  80.         (perform processing as required)
  81.         jmp OldInt21
  82.  
  83. The installation code saves the current contents of the INT 21h vector
  84. in a 32-bit variable called OldInt21, which is known as a "downward
  85. link" or "downlink" because it provides a link "downward" in the chain
  86. of interrupt handlers.  The installation then sets the INT 21h vector
  87. to point to its own interrupt handler at NewInt21.  When an INT 21h
  88. call is subsequently issued, execution is routed to NewInt21, which
  89. performs whatever processing it needs to do.  It then executes a far
  90. jump to the address in OldInt21, allowing previously installed TSRs
  91. (and DOS, of course) to do their work.  The flow of control looks like
  92. this if only one TSR is loaded:
  93.  
  94.                             vector          OldInt21
  95.     application (int 21h) -----------> TSR ----------> DOS
  96.  
  97. To unload itself, the TSR simply resets the INT 21h vector to its
  98. initial contents (i.e., the address in OldInt21).  The TSR's interrupt
  99. handler is now no longer in the chain, and the TSR can be safely
  100. unloaded:
  101.  
  102.                             vector
  103.     application (int 21h) -----------> DOS
  104.  
  105. This scheme works fairly well until the situation arises where more
  106. than one program tries to hook the same vector:
  107.  
  108.           vector           OldInt21A        OldInt21B
  109.     app -----------> TSR A ---------> TSR B ---------> DOS
  110.  
  111. This also works fine--until TSR B wants to unload itself.  B cannot
  112. follow its normal procedure and replace the INT 21h vector with the
  113. contents of its OldInt21.  If it did, the interrupt chain would look
  114. like this:
  115.  
  116.           vector
  117.     app -----------> DOS
  118.  
  119. The problem, obviously, is that TSR A has been unceremoniously removed
  120. >from the interrupt chain; it has been disabled without notice, even
  121. though it remains in memory.  This is obviously an unsatisfactory--and
  122. quite possibly dangerous--situation.
  123.  
  124. Nor can TSR B simply unload itself without fixing INT 21h.  TSR A would
  125. still have TSR B's address stored in its OldInt21; when it has
  126. completed its processing of an INT 21h call, it will jump to the
  127. address where TSR B was at one time--but is no longer--loaded.  The
  128. only unpredictable aspect of the result is which kind of reboot (hard
  129. or soft) will be required.
  130.  
  131. TSR B's only option is to wave its hands and notify the user that it
  132. cannot be unloaded.  This is satisfying to the programmer ("We told you
  133. that you can't do this") but not to the user.
  134.  
  135. The root of the problem is that TSR A has TSR B's address, but not vice
  136. versa.  If TSR B knew where TSR A was keeping its (B's) address, the
  137. resolution would be simple:  B could simply copy its downlink into A's
  138. downlink.
  139.  
  140. Here is a hypothetical memory map:
  141.  
  142.                 ---------------------
  143.                     VECTOR TABLE
  144.     0000:0084   INT21h vector = 1200:0240 --+
  145.                 ----------------------      |
  146.                                             |
  147.                 ----------------------      |
  148.                        TSR A                |
  149.     1200:0240   NewInt21 (int handler) <----+
  150.                         ...
  151.     1200:0642   OldInt21 = 1000:0296   ---+
  152.                 ----------------------    |
  153.                                           |
  154.                 ----------------------    |
  155.                        TSR B              |
  156.     1000:0296   NewInt21 (int handler) <--+
  157.                         ...
  158.     1000:0415   OldInt21 = 0070:1234   ---+
  159.                 ----------------------    |
  160.                                           |
  161.                 ----------------------    |
  162.                         DOS               |
  163.     0070:1234   INT 21h entry point    <--+
  164.                         ...
  165.                 ----------------------
  166.  
  167. The vector table entry for INT 21h points to TSR A's interrupt handler
  168. at 1200:0240.  TSR A's OldInt21 contains TSR B's interrupt handler
  169. address (1000:0296); when A has completed its work, it jumps to B at
  170. that address.  B has DOS's address (0070:1234) in its OldInt21; when it
  171. has finished, it jumps to DOS's address.
  172.  
  173. To take itself out of the chain, all B would have to do would be to put
  174. its downward link (DOS's address, contained in B's OldInt21) into A's
  175. downward link (which currently contains B's address):
  176.  
  177.                 ---------------------
  178.                     VECTOR TABLE
  179.     0000:0084   INT21h vector = 1200:0240 --+
  180.                 ----------------------      |
  181.                                             |
  182.                 ----------------------      |
  183.                        TSR A                |
  184.     1200:0240   NewInt21 (int handler) <----+
  185.                         ...
  186.     1200:0642   OldInt21 = 0070:1234   ---+     <=== change made here
  187.                 ----------------------    |
  188.                                           |
  189.                 ----------------------    |
  190.                        TSR B              |
  191.     1000:0296   NewInt21 (int handler)    |
  192.                         ...               |
  193.     1000:0415   OldInt21 = 0070:1234      |
  194.                 ----------------------    |
  195.                                           |
  196.                 ----------------------    |
  197.                         DOS               |
  198.     0070:1234   INT 21h entry point    <--+
  199.                         ...
  200.                 ----------------------
  201.  
  202. B is now removed from the chain; it could be safely unloaded, and A
  203. will remain active.  The problem, as mentioned, is that B doesn't know
  204. the address of A's OldInt21, so it can't make the correction.
  205.  
  206. THE SOLUTION
  207.  
  208. The solution offered by the interrupt sharing protocol is simplicity
  209. itself: it requires that the downward link pointer be kept at a
  210. specific offset from the interrupt handler entry point.  The entry
  211. point for the first handler in a chain can be found in the interrupt
  212. vector table; in this manner, a chain can be traced from first handler
  213. to last.
  214.  
  215. The offset of the downward link from the entry point turns out to be 2.
  216. Thus, since TSR A's entry point (found in the vector table) is
  217. 1200:0240, his downlink must be located at 1200:0242.  The map would
  218. look like if both programs followed the protocol:
  219.  
  220.                 ---------------------
  221.                     VECTOR TABLE
  222.     0000:0084   INT21h vector = 1200:0240 --+
  223.                 ----------------------      |
  224.                                             |
  225.                 ----------------------      |
  226.                        TSR A                |
  227.     1200:0240   NewInt21 (int handler) <----+
  228.     1200:0242   OldInt21 = 1000:0296   ---+
  229.                         ...               |
  230.                 ----------------------    |
  231.                                           |
  232.                 ----------------------    |
  233.                        TSR B              |
  234.     1000:0296   NewInt21 (int handler) <--+
  235.     1000:0298   OldInt21 = 0070:1234   ---+
  236.                         ...               |
  237.                 ----------------------    |
  238.                                           |
  239.                 ----------------------    |
  240.                         DOS               |
  241.     0070:1234   INT 21h entry point    <--+
  242.                         ...
  243.                 ----------------------
  244.  
  245. The difference between this and the first map shown is that the
  246. addresses of the downlinks can be determined by any external program:
  247. they are no longer private to each TSR.
  248.  
  249. Program B can find A's downlink by simply following the chain (starting
  250. at the address contained in the INT 21h vector), examining the downward
  251. links until the link that points to B's interrupt handler is found.
  252. These links will always be located at the handler entry point + 2. When
  253. B finds a link that points to his handler (at 1000:0296), he has found
  254. what he needs.
  255.  
  256. The remainder of this document fills in the necessary details for
  257. implentation of the protocol.
  258.  
  259. THE ENTRY STRUCTURE
  260.  
  261. The protocol requires that you use a small (18-byte) block of mixed
  262. code and data at your interrupt handler's entry point.  When you take
  263. over an interrupt, you save the current vector in a specific location
  264. within this block and then set the vector to point to the start of the
  265. block.  The first item in the block is a short jump to your interrupt
  266. handler.
  267.  
  268. The block looks like this:
  269.  
  270.     intercept:  jmp short int_handler
  271.     prevhndlr   dd 0
  272.     signature   dw 424Bh
  273.     flag        db 0
  274.                 jmp short hwreset
  275.                 db 7 dup (0)            ; Reserved
  276.     int_handler:
  277.                 ; your interrupt handler starts here...
  278.  
  279. 'intercept' is the address you'll use when you do a SETVEC to intercept
  280. the interrupt vector, i.e., the entry point for your interrupt handler.
  281.  
  282. 'prevhndlr' is set to contain the initial contents of the interrupt
  283. vector at the time you take over.  (This is what we were calling
  284. OldInt21 in the previous sections.)
  285.  
  286. 'signature' must contain 424Bh ("KB") and is used to help identify one
  287. of these blocks.
  288.  
  289. 'flag' is important only if you're taking over a hardware interrupt
  290. that requires an EOI (say, INT 8 or INT 9).  For software interrupts
  291. (INT 16 or INT 21, e.g.), leave it 0. For hardware interrupts, the
  292. first installed handler should set the flag to 80h.  Only the handler
  293. whose flag is 80h is allowed to issue EOI.
  294.  
  295. The 'jmp short hwreset' is pretty much irrelevant to anything software
  296. people will use; it's primarily for hardware manufacturers (allows them
  297. to specify code to be executed to reinitialize the hardware on a system
  298. reset).  However, you must be prepared for the eventuality that this
  299. entry point will be called by someone; do this by simply defining an
  300. HWRESET label with a RETF:
  301.  
  302.     hwreset:    retf
  303.  
  304. Finally, leave the seven reserved bytes as zeroes.
  305.  
  306. INSTALLING INTO THE CHAIN
  307.  
  308. To install into the interrupt chain, simply (a) save the address of the
  309. current interrupt handler in PREVHNDLR, and (b) set the interrupt
  310. vector to point to INTERCEPT.  Your handler is now first in the chain.
  311. This is no different from what you're probably doing now.  (NOTE:  the
  312. code shown here assumes that CS contains the segment of the interrupt
  313. handler.)
  314.  
  315.         ; Save current vector
  316.         mov al,interrupt number
  317.         mov ah,35h
  318.         int 21h                     ; Current vector in ES:BX
  319.         mov word ptr cs:prevhndlr,bx
  320.         mov word ptr cs:prevhndlr+2,es
  321.  
  322.         ; Set new vector
  323.         mov ax,cs
  324.         mov ds,ax
  325.         mov dx,offset intercept     ; DS:DX -> new intercept
  326.         mov ah,25H
  327.         mov al,interrupt number
  328.         int 21h
  329.  
  330. INTERRUPT HANDLER
  331.  
  332. Your interrupt handler, which begins at the label INT_HANDLER, performs
  333. its duties as required.  When you are done processing, check the
  334. contents of PREVHNDLR; if it is nonzero (the usual case), chain to the
  335. previous handler by executing a long jump to the address contained in
  336. PREVHNDLR.  Otherwise, just IRET.  Sample code:
  337.  
  338.     int_handler:
  339.         (do your thing, preserving regs as necessary)
  340.         push ax             ; Is PREVHNDLR 0:0?
  341.         mov ax,word ptr cs:prevhndlr
  342.         or ax,word ptr cs:prevhndlr+2
  343.         pop ax
  344.         jz all_done         ; Yes, PREVHNDLR is 0:0, do IRET
  345.         jmp cs:prevhndlr    ; No, chain to next handler
  346.     all_done:
  347.         ; if you are a hardware handler with flags=80h, do EOI here
  348.         iret
  349.  
  350.     hwreset: retf           ; Don't forget this!
  351.  
  352. It is strongly recommended that the INT_HANDLER label immediately
  353. follow the end of the protocol block, even though this is not required
  354. by the protocol.  Some programs may assume this to be the case and look
  355. for a specific jump distance at offset 1 of the block when attempting
  356. to identify whether or not this is a valid block.  In other words, they
  357. will look for the first item in the block to be a JMP SHORT $+18.
  358.  
  359. It is critical that you chain to the previous handler using the address
  360. stored in PREVHNDLR.  Do not store the address elsewhere and use that
  361. for chaining.  The reason for this is simple:  as discussed in the
  362. introductory sections, one of the main features of the protocol is that
  363. other programs are allowed to find and mess with the contents of your
  364. PREVHNDLR.  In particular, the handler whose address is in your
  365. PREVHNDLR may take himself out of the chain by replacing what's in your
  366. PREVHNDLR with what's in his PREVHNDLR.
  367.  
  368. To recap the introductory sections, suppose you are program C and the
  369. chain currently looks like this:
  370.  
  371.     vector -> C -> B -> A
  372.  
  373. You have program B's address in your PREVHNDLR.  B has program A's
  374. address in his PREVHNDLR.  B can remove himself from the chain by
  375. putting A's address in *YOUR* PREVHNDLR:
  376.  
  377.     vector -> C -> A
  378.  
  379. This will not work if you store B's address somewhere else--B must know
  380. where his address is stored in YOUR code.  There's an example of this
  381. in DISCONNECTING, below.
  382.  
  383. WALKING THE CHAIN
  384.  
  385. To "walk" an interrupt handler chain, get the current vector:
  386.  
  387.         mov al,interrupt number
  388.         mov ah,35h
  389.         int 21h                 ; ES:BX has current
  390.  
  391. Check to see whether ES:BX points to a valid entry structure.  Look
  392. for:
  393.  
  394.         byte ptr ES:[BX]   = 0EBh (jmp short)
  395.         word ptr ES:[BX+6] = 424Bh (signature)
  396.         byte ptr ES:[BX+9] = 0EBh (another jmp short)
  397.  
  398. If all of these match, odds are real good that this is a valid
  399. structure implementing the protocol:  there is a protocol-aware
  400. interrupt handler at ES:BX.  The address of the previous handler is at
  401. ES:[BX+2], so you can find the previous one via
  402.  
  403.         les bx,es:[bx+2]
  404.  
  405. You can continue in this fashion until either (a) ES:BX is zero, or (b)
  406. ES:BX doesn't point to a valid structure (meaning someone isn't
  407. cooperating or you've reached a pointer into DOS or BIOS).  See the
  408. next section for a more complete code example.
  409.  
  410. DISCONNECTING FROM THE CHAIN
  411.  
  412. To remove yourself from the chain, simply walk the chain as above until
  413. either (a) ES:BX points to your own structure, (b) ES:BX points to a
  414. structure whose PREVHNDLR field points to your structure, or (c) ES:BX
  415. does not point to a valid structure.
  416.  
  417. In case (a), you are the last handler registered, and you can simply
  418. reset the interrupt vector to point to the previous handler (the one
  419. whose address is in your PREVHNDLR field).
  420.  
  421. In case (b), someone has registered after you, but you can take
  422. yourself out of the chain by replacing his PREVHNDLR with what you have
  423. stored in your own.
  424.  
  425. In case (c), you cannot safely unload.  A non-protocol handler has
  426. broken the chain.
  427.  
  428. Coding this is not difficult at all, nor does it use much memory.  An
  429. example follows (with some pseudocode to save space).  Assume the
  430. existence of a check_valid_structure subroutine that returns carry set
  431. if ES:BX does not point to a valid protocol structure:
  432.  
  433.     ; Get address of first handler (last loaded)
  434.     mov al,interrupt number
  435.     mov ah,35h
  436.     int 21h                         ; First handler at ES:BX
  437.  
  438.     ; Are we the first handler (case A)?
  439.     if (es = seg INTERCEPT) and (bx = offset INTERCEPT) then
  440.         ; Yes, we are the first handler, just reset the
  441.         ; vector to point to the previous handler
  442.         lds dx,cs:prevhndlr         ; DS:DX -> previous handler
  443.         mov al,(interrupt number)
  444.         mov ah,25h
  445.         int 21h
  446.         jmp unload                  ; Now safe to unload
  447.     end
  448.  
  449.     ; No, walk the chain until case B or C occurs
  450.   L1:
  451.     call check_valid_structure      ; ES:BX -> protocol structure?
  452.     jc chain_busted                 ; No, chain is broken (case C)
  453.     lds dx,dword ptr es:[bx+2]      ; DS:DX = his PREVHNDLR
  454.     if (ds = seg INTERCEPT) and (dx = offset INTERCEPT) then
  455.         ; He points to us (case B).  Set his PREVHNDLR
  456.         ; to contents of our PREVHNDLR.  This takes us
  457.         ; out of the interrupt service chain.
  458.         lds dx,cs:prevhndlr         ; DS:DX -> handler before us
  459.         mov es:[bx+2],dx            ; Beam us up...
  460.         mov es:[bx+4],ds
  461.         jmp unload                  ; Now safe to unload
  462.     end
  463.  
  464.     ; ES:BX handler does not point to us, work backward
  465.     les bx,es:[bx+2]
  466.     jmp L1
  467.  
  468.   chain_busted:
  469.     ; If we get here, we cannot unload safely
  470.     ; Notify user and exit
  471.  
  472.   unload:
  473.     ; Here it is safe to unload
  474.  
  475. As you can see, the code is reasonably short and sweet.  It would be
  476. sensible to implement much of this as subroutines, especially for those
  477. TSRs that intercept more than one interrupt.  In general, you should
  478. check all of the vectors you intercept for "disconnectability" before
  479. disconnecting any of them.  This implies the existence of a "chain
  480. walking" subroutine and a disconnecting subroutine, both of these being
  481. generalized versions of the code shown above.
  482.  
  483. FINAL COMMENTS
  484.  
  485. Note that the protocol allows you to install yourself as other than the
  486. first interrupt handler, and even to re-order a chain.  If for any
  487. reason you don't want to be first, walk the chain and insert yourself
  488. wherever you want by copying someone else's PREVHNDLR into your own,
  489. then putting your address into his.  You are now inserted into the
  490. chain just after him.
  491.  
  492. The code samples given above are generic and are not copied from
  493. working code from my own software.  There may be errors.
  494.  
  495. Chris Dunford 8/6/91
  496.